/*
 *         (C) COPYRIGHT NBI Limited
 *              ALL RIGHT RESERVED
 *
 * File        : fastflow/core/logging.cc
 * Authors     : dzhang
 * Create Time : 2021/09/03 09:44:04
 * Description :
 *
 */

#include "fastflow/core/logging.h"

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

#include <iostream>
#include <string>
#include <unordered_map>

namespace fastflow {

namespace logging {

Logger::Logger(/* args */){};
Logger::~Logger(){};

}  // namespace logging

namespace Internal {

void LogMessage::generate_log_message() {
    time_t now_seconds = time(NULL);
    struct timeval tv_;
    gettimeofday(&tv_, NULL);
    int32 micros_remainder = tv_.tv_usec;
    const size_t time_buffer_size = 30;
    char time_buffer[time_buffer_size];
    strftime(time_buffer, time_buffer_size, "%Y-%m-%d %H:%M:%S", localtime(&now_seconds));

    // TODO(jeff,sanjay): Replace this with something that logs through the env.
    fprintf(stderr, "%s.%06d: %c %s:%d-%s] %s\n", time_buffer, micros_remainder, "DIWEF"[severity_], fname_, line_,
            funcname_, str().c_str());
}

namespace {

int parse_integer(const char *str, size_t size) {
    // Ideally we would use env_var / safe_strto64, but it is
    // hard to use here without pulling in a lot of dependencies,
    // so we use std:istringstream instead
    std::string integer_str(str, size);
    std::istringstream ss(integer_str);
    int level = 0;
    ss >> level;
    return level;
}

// Parse log level (int64) from environment variable (char*)
int64 log_level_str_to_int(const char *ff_env_var_val) {
    if (ff_env_var_val == nullptr) {
        return 0;
    }
    return parse_integer(ff_env_var_val, strlen(ff_env_var_val));
}

// Using StringPiece breaks Windows build.
struct StringData {
    struct Hasher {
        size_t operator()(const StringData &sdata) const {
            // For dependency reasons, we cannot use hash.h here. Use DBJHash instead.
            size_t hash = 5381;
            const char *data = sdata.data;
            for (const char *top = data + sdata.size; data < top; ++data) {
                hash = ((hash << 5) + hash) + (*data);
            }
            return hash;
        }
    };

    StringData() = default;
    StringData(const char *data, size_t size) : data(data), size(size) {}

    bool operator==(const StringData &rhs) const {
        return size == rhs.size && memcmp(data, rhs.data, size) == 0;
    }

    const char *data = nullptr;
    size_t size = 0;
};

using VmoduleMap = std::unordered_map<StringData, int, StringData::Hasher>;

// Returns a mapping from module name to VLOG level, derived from the
// FF_CPP_VMOUDLE environment variable; ownership is transferred to the caller.
VmoduleMap *vmodules_map_from_env() {
    // The value of the env var is supposed to be of the form:
    //    "foo=1,bar=2,baz=3"
    const char *env = getenv("FASTFLOW_CPP_MIN_LOG_LEVEL");
    if (env == nullptr) {
        // If there is no FASTFLOW_CPP_MIN_LOG_LEVEL configuration (most common case),
        // return nullptr so that the ShouldVlogModule() API can fast bail out of
        // it.
        return nullptr;
    }
    // The memory returned by getenv() can be invalidated by following getenv() or
    // setenv() calls. And since we keep references to it in the VmoduleMap in
    // form of StringData objects, make a copy of it.
    const char *env_data = strdup(env);
    VmoduleMap *result = new VmoduleMap();
    while (true) {
        const char *eq = strchr(env_data, '=');
        if (eq == nullptr) {
            break;
        }
        const char *after_eq = eq + 1;

        // Comma either points at the next comma delimiter, or at a null terminator.
        // We check that the integer we parse ends at this delimiter.
        const char *comma = strchr(after_eq, ',');
        const char *new_env_data;
        if (comma == nullptr) {
            comma = strchr(after_eq, '\0');
            new_env_data = comma;
        } else {
            new_env_data = comma + 1;
        }
        (*result)[StringData(env_data, eq - env_data)] = parse_integer(after_eq, comma - after_eq);
        env_data = new_env_data;
    }
    return result;
}

}  // namespace

int64 min_vlog_level_from_env() {
    // We don't want to print logs during fuzzing as that would slow fuzzing down
    // by almost 2x. So, if we are in fuzzing mode (not just running a test), we
    // return a value so that nothing is actually printed. Since VLOG uses <=
    // (see VLOG_IS_ON in logging.h) to see if log messages need to be printed,
    // the value we're interested on to disable printing is 0.
    // See also http://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    return 0;
#else
    const char *env_var_val = getenv("FASTFLOW_CPP_MIN_LOG_LEVEL");
    if (env_var_val == nullptr) {
        return MIN_LOG_LEVEL_DEFAULT;
    }
    return log_level_str_to_int(env_var_val);
#endif
}

LogMessage::LogMessage(const char *fname, int line, const char *func, int severity)
    : fname_(fname), funcname_(func), line_(line), severity_(severity) {}

LogMessage::~LogMessage() {
    // Read the min log level once during the first call to logging.
    static int64 min_log_level = min_vlog_level_from_env();
    if (severity_ >= min_log_level) {
        generate_log_message();
    }
    if (severity_ > WARNING) {
        exit(0);
    }
}

int64 LogMessage::min_vlog_level() {
    static int64 min_vlog_level = min_vlog_level_from_env();
    return min_vlog_level;
}

bool LogMessage::vmodule_activated(const char *fname, int level) {
    if (level >= min_vlog_level()) {
        return true;
    }
    static VmoduleMap *vmodules = vmodules_map_from_env();
    if (FASTFLOW_PREDICT_TRUE(vmodules == nullptr)) {
        return false;
    }
    const char *last_slash = strrchr(fname, '/');
    const char *module_start = last_slash == nullptr ? fname : last_slash + 1;
    const char *dot_after = strchr(module_start, '.');
    const char *module_limit = dot_after == nullptr ? strchr(fname, '\0') : dot_after;
    StringData module(module_start, module_limit - module_start);
    auto it = vmodules->find(module);
    return it != vmodules->end() && it->second >= level;
}

LogMessageFatal::LogMessageFatal(const char *file, int line) : LogMessage(file, line, "", FATAL) {}
LogMessageFatal::~LogMessageFatal() {
    // abort() ensures we don't return (we promised we would not via
    // ATTRIBUTE_NORETURN).
    generate_log_message();
    abort();
}

void log_string(const char *fname, int line, int severity, const std::string &message) {
    LogMessage(fname, line, "", severity) << message;
}

}  // namespace Internal
}  // namespace fastflow
